package service

import (
	"context"
	"gitee.com/crack007/goose"
	"github.com/crack007/go-im/pb"
	"github.com/crack007/go-im/src/common/model"
	"github.com/crack007/go-im/src/common/service"
	"go.uber.org/atomic"
	"google.golang.org/grpc"
	"math/rand"
	"strconv"
	"sync"
	"time"
)

type sentinelService struct {
	CurrentNode   *model.SentinelModel // 当前sentinel节点信息
	TotalSentinel int                  // sentinel总节点数量(包括leader和自己)
	lock          sync.Mutex

	workNodeClusterModel *model.NodeClusterModel // 维护所有work和sentinel节点
	voteQueue            chan *model.VoteModel
	voteResult           chan *model.VoteModel
	VoteRecord           map[uint64]string // 当前已投票记录
	Epoch                *atomic.Int64
}

func NewSentinelService() *sentinelService {
	return &sentinelService{
		CurrentNode:          model.NewSentinelModel(),
		workNodeClusterModel: model.NewNodeClusterModel(),
		voteQueue:            make(chan *model.VoteModel),
		voteResult:           make(chan *model.VoteModel),
		VoteRecord:           make(map[uint64]string),
		Epoch:                atomic.NewInt64(0),
	}
}
func (s *sentinelService) ConnectMaster(ip string, port uint16) error {
	target := ip + ":" + strconv.Itoa(int(port))
	conn, err := grpc.Dial(target, grpc.WithInsecure(), grpc.WithBlock())
	if err != nil {
		return err
	}
	client := pb.NewNodeServiceClient(conn)
	sentinelInfo := &pb.SentinelInfo{}
	sentinelInfo.Id = s.CurrentNode.Id
	sentinelInfo.Ip = s.CurrentNode.Ip
	sentinelInfo.Port = uint32(s.CurrentNode.Port)

	nodeMapInfo, err := client.OnConnectSentinel(context.Background(), sentinelInfo)

	if err != nil {
		return err
	}
	for _, nodeInfo := range nodeMapInfo.NodeList {
		if nodeInfo.IsMaster {
			goose.GetLogger().Debug("Master node-> id:%s", nodeInfo.Id)
		}
		nodeModel := model.NewNodeModel()
		nodeModel.WsPort = uint16(nodeInfo.WsPort)
		nodeModel.Port = uint16(nodeInfo.Port)
		nodeModel.Ip = nodeInfo.Ip
		nodeModel.Id = nodeInfo.Id
		nodeModel.IsMaster = nodeInfo.IsMaster
		err = SentinelConnection.ConnectNode(nodeModel, s.CurrentNode)
		if err != nil {
			return err
		}
	}

	// 广播所有的sentinel节点
	s.TotalSentinel = len(nodeMapInfo.SentinelList)
	for _, sentinel := range nodeMapInfo.SentinelList {
		if sentinel.Id == s.CurrentNode.Id {
			continue
		}
		sentinelModel := model.NewSentinelModel()
		sentinelModel.Port = uint16(sentinel.Port)
		sentinelModel.Ip = sentinel.Ip
		sentinelModel.Id = sentinel.Id
		err = SentinelConnection.ConnectSentinel(sentinelModel, SentinelService.CurrentNode)
		if err != nil {
			return err
		}
	}

	return nil
}

// 投票成功，进行故障转移
func (s *sentinelService) ChangeToLeader(removeNodeModel *model.NodeModel) {
	goose.GetLogger().Debug("ChangeToLeader")
	nodeInfo := model.GetNodeInfo(removeNodeModel)
	SentinelConnection.Broadcast(func(sentinelModel *model.SentinelModel) {
		sentinelModel.Client.WorkerOffline(context.Background(), nodeInfo)
	})
	service.NodeService.RemoveCache(removeNodeModel)
	s.RemoveWork(removeNodeModel.Id)
	// 选取剩下的节点
	if len(s.workNodeClusterModel.NodeMap) == 0 {
		// TODO 没有可用节点处理
		goose.GetLogger().Warn("没有可用的节点了")
		return
	}
	for _, node := range s.workNodeClusterModel.NodeMap {
		goose.GetLogger().Debug("启用节点-> id:%s", node.Id)
		node.IsMaster = true
		s.UpdateWork(node)
		masterNode := model.GetNodeInfo(node)
		SentinelConnection.Broadcast(func(sentinelModel *model.SentinelModel) {
			sentinelModel.Client.WorkerUpdate(context.Background(), masterNode)
		})
		service.NodeService.Store(node)
		break
	}

}
func (s *sentinelService) SetNodeOffline(id string) {
	s.lock.Lock()
	defer s.lock.Unlock()
	goose.GetLogger().Debug("Node Offline-> id:%s", id)
	s.RemoveWork(id)
}
func (s *sentinelService) AddSentinelNode(sentinel *model.SentinelModel) {
	s.lock.Lock()
	defer s.lock.Unlock()
	goose.GetLogger().Debug("AddSentinelNode-> id:%s", sentinel.Id)
	s.workNodeClusterModel.SentinelMap[sentinel.Id] = sentinel
}
func (s *sentinelService) IsNodeExist(id string) bool {
	_, ok := s.workNodeClusterModel.NodeMap[id]
	return ok
}
func (s *sentinelService) IsSentinelExist(id string) bool {
	_, ok := s.workNodeClusterModel.SentinelMap[id]
	return ok
}
func (s *sentinelService) AddNode(nodeModel *model.NodeModel) {
	s.workNodeClusterModel.NodeMap[nodeModel.Id] = nodeModel
}
func (s *sentinelService) Vote(nodeModel *model.NodeModel) {
	// TODO 配置最小Sentinel节点数量
	minSentinel := 3
	SentinelService.TotalSentinel = SentinelService.GetSentinelCount()
	if SentinelService.TotalSentinel < minSentinel {
		return
	}

	goose.GetLogger().Debug("主动发起投票")
	epoch := s.Epoch.Inc()
	SentinelService.VoteRecord[uint64(epoch)] = s.CurrentNode.Id
	ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*300)
	defer cancel()
	voteReq := &pb.VoteReq{}
	voteReq.LeaderId = s.CurrentNode.Id
	voteReq.Epoch = uint64(epoch)
	nodeTotal := SentinelService.GetSentinelCount()
	voteTotal := 1
	SentinelConnection.Broadcast(func(sentinelModel *model.SentinelModel) {
		voteResp, err := sentinelModel.Client.Vote(ctx, voteReq)
		if err != nil {
			goose.GetLogger().Error(err)
			return
		}
		if voteResp.Epoch == voteReq.Epoch && voteResp.LeaderId == voteReq.LeaderId {
			voteTotal++
		}
	})

	goose.GetLogger().Debug("投票结果 Epoch:%d voteTotal:%d nodeTotal:%d", voteReq.Epoch, voteTotal, nodeTotal)
	if voteTotal > (nodeTotal/2)+1 {
		s.ChangeToLeader(nodeModel)
		return
	}
	goose.GetLogger().Debug("投票失败")
	rand.Seed(time.Now().Unix())
	t := time.Duration(rand.Int31n(500)+500) * time.Millisecond
	time.Sleep(t)
	s.Vote(nodeModel)
}

// 后台处理投票逻辑队列
func (s *sentinelService) VoteProcess() {
	for voteReq := range s.voteQueue {
		// 判断配置纪元是否已投过
		voteResp := model.NewVoteModel()
		voteResp.Epoch = voteReq.Epoch
		if v, ok := s.VoteRecord[voteReq.Epoch]; ok {
			voteResp.LeaderId = v
		} else {
			s.Epoch.Inc()
			// TODO bug map会一直膨胀
			s.VoteRecord[voteReq.Epoch] = voteReq.LeaderId
			voteResp.LeaderId = voteReq.LeaderId
		}
		s.voteResult <- voteResp
	}
}

// 投递投票处理队列
func (s *sentinelService) PushVote(voteModel *model.VoteModel) <-chan *model.VoteModel {
	s.voteQueue <- voteModel
	return s.voteResult
}

func (s *sentinelService) GetSentinelCount() int {
	return len(s.workNodeClusterModel.SentinelMap)
}

func (s *sentinelService) RemoveWork(id string) {
	delete(s.workNodeClusterModel.NodeMap, id)
}

func (s *sentinelService) UpdateWork(nodeModel *model.NodeModel) {
	s.workNodeClusterModel.NodeMap[nodeModel.Id] = nodeModel
}
